package com.cwnu.sw.cys.core.websocket.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import com.cwnu.sw.cys.core.common.config.ThreadPoolConfig;
import com.cwnu.sw.cys.core.common.event.UserOnlineEvent;
import com.cwnu.sw.cys.core.common.utils.NettyUtil;
import com.cwnu.sw.cys.core.user.dao.UserDao;
import com.cwnu.sw.cys.core.user.domain.entity.IpInfo;
import com.cwnu.sw.cys.core.user.domain.entity.User;
import com.cwnu.sw.cys.core.user.domain.enums.RoleEnum;
import com.cwnu.sw.cys.core.user.service.LoginService;
import com.cwnu.sw.cys.core.user.service.RoleService;
import com.cwnu.sw.cys.core.websocket.domain.vo.req.WSAuthorizeReq;
import com.cwnu.sw.cys.core.websocket.service.adapter.WebSocketBuilder;
import com.cwnu.sw.cys.core.websocket.domain.dto.WSChannelExtraDTO;
import com.cwnu.sw.cys.core.websocket.domain.vo.req.WSBaseReq;
import com.cwnu.sw.cys.core.websocket.domain.vo.resp.WSBaseResp;
import com.cwnu.sw.cys.core.websocket.service.WebSocketService;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

import static java.lang.Integer.MAX_VALUE;

@Service
@Slf4j
public class WebSocketServiceImpl implements WebSocketService {

    @Autowired
    private WxMpService wxMpService;
    // 管理所有在线的channel
    public static final Duration DURATION = Duration.ofSeconds(300);
    public static final int MAXIMUM_SIZE = 10000;
    /**
     * 等待登录的channel
     * */
    public static final Cache<Integer, Channel> WAIT_LOGIN_MAP = Caffeine.newBuilder()
            .maximumSize(MAXIMUM_SIZE)
            .expireAfterWrite(DURATION)
            .build();
    private static final ConcurrentHashMap<Channel, WSChannelExtraDTO> ONLINE_CHANNEL_MAP =  new ConcurrentHashMap<>();

    @Autowired
    private LoginService loginService;
    @Autowired
    private UserDao userDao;
    @Autowired
    @Qualifier(ThreadPoolConfig.WS_EXECUTOR)
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private RoleService roleService;

    @Override
    public void connect(Channel channel) {
        ONLINE_CHANNEL_MAP.put(channel, new WSChannelExtraDTO());
    }

    @Override
    public void handleLoginReq(Channel channel, WSBaseReq req) throws WxErrorException {
        // 生成随机的码
        Integer code = gennerateLoginCode();
        // 2025年3月22日 15:11:34 可恶啊 忘记放进MAP了，搞了几天，一直没看出来错误，一行一行找
        WAIT_LOGIN_MAP.put(code, channel);
        // 找微信申请带参数的二维码
        WxMpQrCodeTicket wxMpQrCodeTicket = null;

        wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) DURATION.getSeconds());

        sendMessage(channel, WebSocketBuilder.build(wxMpQrCodeTicket));
        // 把码返回给前端
    }

    @Override
    public void remove(Channel channel) {
        ONLINE_CHANNEL_MAP.put(channel,new WSChannelExtraDTO());
        //TODO 用户下线
    }




    /**
     * @param code
     * @param uid
     */
    @Override
    public Boolean scanLoginSuccess(Integer code, Long uid) {
        Channel channel = WAIT_LOGIN_MAP.getIfPresent(code);
        if (Objects.isNull(channel)) {
            log.info("code:{} is invalid", code);
            return Boolean.FALSE;
        }
        User user = userDao.getById(uid);
        WAIT_LOGIN_MAP.invalidate(code);

        String token = loginService.login(uid);
        log.info("login success, uid:{}, token:{}", uid, token);
        // 增加调试日志，确保 loginSuccess 方法被调用
        log.debug("Calling loginSuccess for uid: {}", uid);
        loginSuccess(channel, user, token);
        return Boolean.TRUE;
    }

    private void loginSuccess(Channel channel, User byId, String token) {
        WSChannelExtraDTO wsChannelExtraDTO = ONLINE_CHANNEL_MAP.get(channel);
        wsChannelExtraDTO.setUid(byId.getId());
        log.info("login success, uid:{}, token:{}", byId.getId(), token);

        boolean hasPower = roleService.hasPower(byId.getId(), RoleEnum.CHAT_MANAGER);
        // 增加调试日志，确保消息构建正确
        WSBaseResp<?> resp = WebSocketBuilder.buildResp(byId, token, hasPower);
        log.debug("Generated login success resp: {}", resp);
        sendMessage(channel, resp);

        // 用户在线事件
        byId.setLastOptTime(new Date());
        IpInfo ipInfo = new IpInfo();

        byId.refreshIp(NettyUtil.getAttr(channel, NettyUtil.IP));
        applicationEventPublisher.publishEvent(new UserOnlineEvent(byId));
    }

    private void sendMessage(Channel channel, WSBaseResp<?> resp) {
        // 增加调试日志，确保消息被正确序列化并发送
        String message = JSON.toJSONString(resp);
        log.debug("Sending message to client: {}", message);
        channel.writeAndFlush(new TextWebSocketFrame(message));
    }

    /**
     * @param channel
     * @param wsAuthorizeReq
     */
    @Override
    public void
    authorize(Channel channel, WSAuthorizeReq wsAuthorizeReq) {
        String token = wsAuthorizeReq.getToken();
        boolean valid = loginService.verify(token);
        if(valid){
            User byId = userDao.getById(loginService.getValidUid(token));
            log.info("user is valid");
            loginSuccess(channel,byId,token);
        }else{
            sendMessage(channel, WebSocketBuilder.buildInvalidTokenResp());
        }

    }

    @Override
    public Boolean scanSuccess(Integer loginCode) {

        Channel channel = WAIT_LOGIN_MAP.getIfPresent(loginCode);
        boolean present;
        present = Objects.nonNull(channel);
        log.info("Objects.nonNull(channel) status :{}", present);
        if (present) {
            sendMessage(channel, WebSocketBuilder.buildScanSuccessResp());
            log.info("scan success, code:{}", loginCode);
            return Boolean.TRUE;
        }
        log.info("code:{} is invalid", loginCode);
        return Boolean.FALSE;
    }

    private Integer gennerateLoginCode() {
        Integer loginCode = RandomUtil.randomInt(MAX_VALUE);
        while (Objects.nonNull(WAIT_LOGIN_MAP.getIfPresent(loginCode))) {
            loginCode = RandomUtil.randomInt(MAX_VALUE);
        }
        return loginCode;
    }
    @Override
    public void sendToAllOnline(WSBaseResp<?> wsBaseResp, Long skipUid) {
        ONLINE_CHANNEL_MAP.forEach((channel, ext) -> {
            if (Objects.nonNull(skipUid) && Objects.equals(ext.getUid(), skipUid)) {
                return;
            }
            threadPoolTaskExecutor.execute(() -> sendMessage(channel, wsBaseResp));
        });
    }
}
